﻿using AiMoBridge.Paths;
using AiMoWindows.Localization;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;
using System.Text;
namespace AiMoWindows.Compiler
{
    class RhinoScriptsCompiler
    {
        readonly Dictionary<string, CompiledResult> CachedCompiledResult = new Dictionary<string, CompiledResult>();
        readonly Dictionary<string, string> CachedFailures = new Dictionary<string, string>();

        HashSet<MetadataReference> DefaultReferenceAssemblies { get; }
        public RhinoScriptsCompiler()
        {
            DefaultReferenceAssemblies = ReferenceAssemblis.DefaultAssemblies.ToHashSet();
        }
        /// <summary>
        /// 获取当前ScriptSource以及Compiler的默认Dll
        /// </summary>
        public MetadataReference[] GetAllReference(ICompilerScript scriptSource)
        {
            HashSet<MetadataReference> compileReferences = DefaultReferenceAssemblies.ToHashSet();
            string[] referenceAssembles = scriptSource.GetReferenceAssemblies();
            foreach (string file in referenceAssembles)
            {
                if (System.IO.File.Exists(file))
                {
                    compileReferences.Add(ReferenceAssemblis.CreateMetadataReference(file));
                }
            }
            return compileReferences.ToArray();
        }

        public CompiledResult CompileScriptInstance(ICompilerScript scriptSource, StringBuilder compileErrors)
        {
            return CompileScriptInstance(scriptSource, compileErrors, out _);
        }
        public CompiledResult CompileScriptInstance(ICompilerScript scriptSource, StringBuilder compileErrors, out bool duplicate)
        {
            duplicate = false;
            string key = scriptSource.GetScriptHash();
            key += scriptSource.GetReferenceExistedChangedHash();
            if (CachedCompiledResult.TryGetValue(key, out CompiledResult duplicateResult))
            {
                duplicate = true;
                return duplicateResult;
            }
            else if (CachedFailures.TryGetValue(key, out string? value2))
            {
                duplicate = true;
                compileErrors.Append(value2);
                return CompiledResult.Unset;
            }

            CompileCodeUnit[] sources = scriptSource.GetCodeForCompiler();

            if (!CompileByRoslyn(sources, GetAllReference(scriptSource), compileErrors, out CompiledResult pathResult))
            {
                compileErrors.Insert(0, Local.Get("Compile Error:") + Environment.NewLine);
                CachedFailures[key] = compileErrors.ToString();
                return CompiledResult.Unset;
            }
            else
            {
                CachedCompiledResult.TryAdd(key, pathResult);
                scriptSource.OnCompileSuccess(pathResult);
                return pathResult;
            }
        }
        string GetErrorString(Diagnostic diagnostic)
        {
            int lineNo = diagnostic.Location.GetLineSpan().StartLinePosition.Line + 1;
            string message = diagnostic.GetMessage();
            string errorString = $"[{diagnostic.Id}] Line {lineNo}: {message}";
            if (!string.IsNullOrEmpty(diagnostic.Location?.SourceTree?.FilePath)) errorString += $" (from: {diagnostic.Location.SourceTree.FilePath})";
            return errorString;
        }
        bool CompileByRoslyn(CompileCodeUnit[] sources, MetadataReference[] metadataReferences, StringBuilder errors, out CompiledResult pathResult)
        {
            PathManager.GetTempDllPath(out string fileName, out string dllPath, out string pdbPath);
            pathResult = new CompiledResult(dllPath, pdbPath);
            if (sources.Length == 0)
            {
                errors.AppendLine("source is empty");
                return false;
            }
            bool re = true;
            CSharpParseOptions parseOptions = new CSharpParseOptions(LanguageVersion.LatestMajor);
            SyntaxTree[] syntaxTrees = sources.Select(unit => CSharpSyntaxTree.ParseText(unit.Content, options: parseOptions, path: unit.FilePath, Encoding.UTF8)).ToArray();
            foreach (SyntaxTree syntaxTree in syntaxTrees)
            {
                foreach (Diagnostic error in syntaxTree.GetDiagnostics().Where(x => x.Severity == DiagnosticSeverity.Error))
                {
                    errors.AppendLine(GetErrorString(error));
                    re = false;
                }
            }
            if (!re) return false;
            CSharpCompilationOptions compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, optimizationLevel: OptimizationLevel.Debug, scriptClassName: "Script_Instance");

            CSharpCompilation compilation = CSharpCompilation.Create(fileName, syntaxTrees, metadataReferences, compilationOptions);
            foreach (Diagnostic error in compilation.GetDiagnostics().Where(x => x.Severity == DiagnosticSeverity.Error))
            {
                errors.AppendLine(GetErrorString(error));
                re = false;
            }
            if (re)
            {
                EmitResult result = compilation.Emit(pathResult.DllPath, pdbPath: pathResult.PdbPath);
                if (result.Success)
                {
                    return true;
                }
                foreach (Diagnostic error in result.Diagnostics)
                {
                    errors.AppendLine(GetErrorString(error));
                }
                return false;
            }
            return re;
        }

    }
}
